import sys
import numpy as np
import pyqtgraph.opengl as gl
from PyQt5.QtWidgets import (
    QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, 
    QLineEdit, QPushButton, QTableWidget, QTableWidgetItem, 
    QHeaderView, QGroupBox, QMessageBox, QApplication
)
from PyQt5.QtCore import Qt
from algorithm import calculate_loading_plan
from pyqtgraph.opengl.items.GLMeshItem import GLMeshItem

class ContainerLoadingApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("集装箱装货计算系统")
        self.setGeometry(100, 100, 1200, 800)
        
        # 初始化货物数据列表
        self.cargo_data = []
        
        # 创建主部件和布局
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.main_layout = QHBoxLayout(self.central_widget)
        
        # 创建左侧控制面板
        self.control_panel = QWidget()
        self.control_layout = QVBoxLayout(self.control_panel)
        self.main_layout.addWidget(self.control_panel, 1)
        
        # 创建集装箱参数输入区域
        self.create_container_params_group()
        
        # 创建货物列表区域
        self.create_cargo_list_group()
        
        # 创建计算按钮
        self.calculate_btn = QPushButton("计算装货方案")
        self.calculate_btn.clicked.connect(self.calculate_loading)
        self.control_layout.addWidget(self.calculate_btn)
        
        # 创建右侧3D预览区域
        self.create_3d_viewer()
        self.main_layout.addWidget(self.viewer, 2)
        
    def create_container_params_group(self):
        """创建集装箱参数输入组"""
        group = QGroupBox("集装箱参数")
        layout = QFormLayout()
        
        self.container_length = QLineEdit("12.0")
        self.container_width = QLineEdit("2.4")
        self.container_height = QLineEdit("2.4")
        self.max_weight = QLineEdit("28000")
        self.front_axle_limit = QLineEdit("14000")
        self.rear_axle_limit = QLineEdit("14000")
        self.front_length = QLineEdit("6.0")
        
        layout.addRow("长度(米):", self.container_length)
        layout.addRow("宽度(米):", self.container_width)
        layout.addRow("高度(米):", self.container_height)
        layout.addRow("总重量限制(kg):", self.max_weight)
        layout.addRow("前轴重量限制(kg):", self.front_axle_limit)
        layout.addRow("后轴重量限制(kg):", self.rear_axle_limit)
        layout.addRow("前轴控制区域(米):", self.front_length)
        
        group.setLayout(layout)
        self.control_layout.addWidget(group)
        
    def create_cargo_list_group(self):
        """创建货物列表区域"""
        group = QGroupBox("货物列表")
        layout = QVBoxLayout()
        
        # 创建货物表格
        self.cargo_table = QTableWidget(0, 6)
        self.cargo_table.setHorizontalHeaderLabels(["货物ID", "长度(米)", "宽度(米)", "高度(米)", "重量(kg)", "数量"])
        self.cargo_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        layout.addWidget(self.cargo_table)
        
        # 创建按钮布局
        btn_layout = QHBoxLayout()
        
        self.add_cargo_btn = QPushButton("添加货物")
        self.add_cargo_btn.clicked.connect(self.add_cargo)
        btn_layout.addWidget(self.add_cargo_btn)
        
        self.remove_cargo_btn = QPushButton("删除选中货物")
        self.remove_cargo_btn.clicked.connect(self.remove_cargo)
        btn_layout.addWidget(self.remove_cargo_btn)
        
        layout.addLayout(btn_layout)
        group.setLayout(layout)
        self.control_layout.addWidget(group)
        
    def create_3d_viewer(self):
        """创建3D预览区域"""
        self.viewer = gl.GLViewWidget()
        self.viewer.setCameraPosition(distance=40)
        self.viewer.mousePressEvent = self.handle_mouse_click
        
        # 添加网格
        grid = gl.GLGridItem()
        grid.scale(2, 2, 1)
        self.viewer.addItem(grid)
        
        # 添加坐标轴
        axis = gl.GLAxisItem()
        axis.setSize(10, 10, 10)
        self.viewer.addItem(axis)
        
        # 存储货物项用于点击检测
        self.cargo_items = []
        
    def add_cargo(self):
        """添加货物到列表"""
        row = self.cargo_table.rowCount()
        self.cargo_table.insertRow(row)
        
        # 默认值
        self.cargo_table.setItem(row, 0, QTableWidgetItem(f"C{row+1}"))
        self.cargo_table.setItem(row, 1, QTableWidgetItem("1.0"))
        self.cargo_table.setItem(row, 2, QTableWidgetItem("1.0"))
        self.cargo_table.setItem(row, 3, QTableWidgetItem("1.0"))
        self.cargo_table.setItem(row, 4, QTableWidgetItem("500"))
        self.cargo_table.setItem(row, 5, QTableWidgetItem("1"))
        
    def remove_cargo(self):
        """删除选中的货物"""
        selected_rows = set()
        for item in self.cargo_table.selectedItems():
            selected_rows.add(item.row())
        
        for row in sorted(selected_rows, reverse=True):
            self.cargo_table.removeRow(row)
        
    def get_container_info(self):
        """从界面获取集装箱信息"""
        try:
            return {
                'length': float(self.container_length.text()),
                'width': float(self.container_width.text()),
                'height': float(self.container_height.text()),
                'max_weight': float(self.max_weight.text()),
                'front_axle_limit': float(self.front_axle_limit.text()),
                'rear_axle_limit': float(self.rear_axle_limit.text()),
                'front_length': float(self.front_length.text()),
                'rear_length': float(self.container_length.text()) - float(self.front_length.text())
            }
        except ValueError:
            QMessageBox.critical(self, "输入错误", "请确保所有集装箱参数都是有效的数字")
            return None
        
    def get_cargo_list(self):
        """从表格获取货物列表"""
        cargo_list = []
        for row in range(self.cargo_table.rowCount()):
            items = []
            for col in range(self.cargo_table.columnCount()):
                item = self.cargo_table.item(row, col)
                if item is None:
                    QMessageBox.warning(self, '输入错误', f'第{row+1}行第{col+1}列数据为空，请检查')
                    return None
                items.append(item.text())
            
            try:
                name = items[0]
                length = float(items[1])
                width = float(items[2])
                height = float(items[3])
                weight = float(items[4])
                quantity = int(items[5])
            except ValueError as e:
                QMessageBox.warning(self, '数据格式错误', f'第{row+1}行数据格式错误: {str(e)}')
                return None
            
            # 根据数量添加货物
            for _ in range(quantity):
                volume = length * width * height
                cargo_list.append({
                    'name': name,
                    'length': length,
                    'width': width,
                    'height': height,
                    'weight': weight,
                    'volume': volume
                })
        
        return sorted(cargo_list, key=lambda x: x['volume'], reverse=True)
        
    def calculate_loading(self):
        """计算装货方案并更新3D视图"""
        # 清除现有3D对象
        for item in self.viewer.items[2:]:  # 保留网格和坐标轴
            self.viewer.removeItem(item)
        
        # 获取集装箱和货物信息
        container = self.get_container_info()
        if not container:
            return
        
        cargo_list = self.get_cargo_list()
        if not cargo_list:
            return
        
        # 计算装货方案
        loading_plan = calculate_loading_plan(container, cargo_list)
        
        # 绘制集装箱
        self.draw_container(container)
        
        # 绘制货物
        self.draw_cargoes(loading_plan[:-1], container)
        
        # 显示利用率信息
        utilization = loading_plan[-1]['utilization']
        QMessageBox.information(self, "计算完成", f"装货方案计算完成，空间利用率: {utilization:.2f}%")
        
    def draw_container(self, container):
        """绘制集装箱3D模型"""
        # 绘制集装箱边框
        vertices = np.array([
            [0, 0, 0],
            [container['length'], 0, 0],
            [container['length'], container['width'], 0],
            [0, container['width'], 0],
            [0, 0, container['height']],
            [container['length'], 0, container['height']],
            [container['length'], container['width'], container['height']],
            [0, container['width'], container['height']],
        ])
        
        edges = np.array([
            [0, 1], [1, 2], [2, 3], [3, 0],  # 底面
            [4, 5], [5, 6], [6, 7], [7, 4],  # 顶面
            [0, 4], [1, 5], [2, 6], [3, 7]   # 连接边
        ])
        
        colors = np.array([[0.5, 0.5, 0.5, 1] for _ in range(len(edges))])
        
        # 重构顶点数据以支持多线段绘制
        line_segments = []
        for edge in edges:
            line_segments.append(vertices[edge[0]])
            line_segments.append(vertices[edge[1]])
            line_segments.append([np.nan, np.nan, np.nan])  # 使用NaN分隔线段
        
        pos = np.array(line_segments)
        container_frame = gl.GLLinePlotItem(pos=pos, color=colors, width=2, mode='lines')
        self.viewer.addItem(container_frame)
        
        # 绘制前后轴区域分隔线
        separator_segments = [
            [container['front_length'], 0, 0],
            [container['front_length'], container['width'], 0],
            [np.nan, np.nan, np.nan],
            [container['front_length'], container['width'], 0],
            [container['front_length'], container['width'], container['height']],
            [np.nan, np.nan, np.nan],
            [container['front_length'], container['width'], container['height']],
            [container['front_length'], 0, container['height']],
            [np.nan, np.nan, np.nan],
            [container['front_length'], 0, container['height']],
            [container['front_length'], 0, 0]
        ]
        
        separator_pos = np.array(separator_segments)
        separator_mesh = gl.GLLinePlotItem(pos=separator_pos, color=[1, 0, 0, 1], width=1, mode='lines')
        self.viewer.addItem(separator_mesh)
        
    def draw_cargoes(self, cargoes, container):
        # 清除现有货物
        self.viewer.clear()
        
        # 绘制集装箱
        self.draw_container(container)
        
        # 绘制货物
        colors = [
            [1, 0, 0, 0.5],   # 红色
            [0, 1, 0, 0.5],   # 绿色
            [0, 0, 1, 0.5],   # 蓝色
            [1, 1, 0, 0.5],   # 黄色
            [1, 0, 1, 0.5],   # 紫色
            [0, 1, 1, 0.5]    # 青色
        ]
        
        from OpenGL.GL import gl
        for i, cargo in enumerate(cargoes):
            # 获取货物尺寸和位置
            length = cargo['length']
            width = cargo['width']
            height = cargo['height']
            x, y, z = cargo['position']
            
            # 选择颜色
            color = colors[i % len(colors)]
            
            # 定义立方体顶点
            vertices = [
                # 底部四个顶点
                [x, y, z],
                [x + length, y, z],
                [x + length, y + width, z],
                [x, y + width, z],
                # 顶部四个顶点
                [x, y, z + height],
                [x + length, y, z + height],
                [x + length, y + width, z + height],
                [x, y + width, z + height],
            ]
            
            # 定义三角形面（每个面由两个三角形组成）
            faces = [
                # 底面
                [0, 1, 2], [0, 2, 3],
                # 顶面
                [4, 5, 6], [4, 6, 7],
                # 前面
                [0, 1, 5], [0, 5, 4],
                # 后面
                [2, 3, 7], [2, 7, 6],
                # 右面
                [1, 2, 6], [1, 6, 5],
                # 左面
                [3, 0, 4], [3, 4, 7],
            ]
            
            mesh_data = gl.MeshData(vertexes=vertices, faces=faces)
            from OpenGL.GL import gl
            cargo_mesh = GLMeshItem(
                meshdata=mesh_data,
                smooth=False,
                color=[color[0], color[1], color[2], 0.9],  # 进一步提高了不透明度
                drawEdges=True,
                edgeColor=[0, 0, 0, 0.8],  # 降低了边框的不透明度
                edgeWidth=1,
                blend=True,  # 启用混合模式
                blendFunc=(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA),  # 设置标准透明混合
                depthValue=1,
                depthMask=False  # 透明物体不写入深度缓冲
            )
            
            # 设置位置和缩放
            cargo_mesh.translate(x + length/2, y + width/2, z + height/2)
            cargo_mesh.scale(length, width, height)
            self.viewer.addItem(cargo_mesh)
            
            # 存储货物数据和图形项用于点击检测
            self.cargo_items.append({
                'mesh': cargo_mesh,
                'data': cargo
            })
    def handle_mouse_click(self, event):
        """处理鼠标点击事件，检测是否点击了货物"""
        if event.button() != Qt.MouseButton.LeftButton:
            return
        
        # 获取点击位置的射线
        pos = event.pos()
        # 手动计算射线 (兼容pyqtgraph 0.13.3        import math
        from PyQt5 import QtGui
        
        # 获取视口尺寸
        w = self.viewer.width()
        h = self.viewer.height()
        
        # 获取相机参数
        eye = self.viewer.cameraPosition()
        center = self.viewer.opts['center']
        up = QtGui.QVector3D(self.viewer.opts.get('up', [0, 1, 0]))
        
        # 计算视图方向和相机坐标系
        view_dir = (center - eye).normalized()
        right = QtGui.QVector3D.crossProduct(view_dir, up).normalized()
        up = QtGui.QVector3D.crossProduct(right, view_dir).normalized()
        
        # 相机参数
        fov = 60  # 假设垂直视场角为60度
        aspect = w / h
        tan_half_fov = math.tan(math.radians(fov) / 2)
        
        # 将鼠标坐标转换为标准化设备坐标(NDC)
        x_ndc = (pos.x() / w) * 2 - 1
        y_ndc = 1 - (pos.y() / h) * 2
        
        # 计算相机空间中的射线方向
        ray_dir_cam = QtGui.QVector3D(
            x_ndc * tan_half_fov * aspect,
            y_ndc * tan_half_fov,
            -1
        ).normalized()
        
        # 转换到世界空间
        ray_direction = QtGui.QVector3D(
            right.x() * ray_dir_cam.x() + up.x() * ray_dir_cam.y() + view_dir.x() * ray_dir_cam.z(),
            right.y() * ray_dir_cam.x() + up.y() * ray_dir_cam.y() + view_dir.y() * ray_dir_cam.z(),
            right.z() * ray_dir_cam.x() + up.z() * ray_dir_cam.y() + view_dir.z() * ray_dir_cam.z()
        ).normalized()
        
        ray_origin = eye
        # 已通过相机参数计算出射线方向
        ray_direction = ray_direction
        
        # 检查与每个货物的碰撞
        for cargo in self.cargo_items:
            mesh = cargo['mesh']
            data = cargo['data']
            
            # 获取货物的世界坐标系位置和缩放
            pos = mesh.opts['position']
            scale = mesh.opts['scale']
            
            # 简化的碰撞检测
            min_x = pos[0] - scale[0]/2
            max_x = pos[0] + scale[0]/2
            min_y = pos[1] - scale[1]/2
            max_y = pos[1] + scale[1]/2
            min_z = pos[2] - scale[2]/2
            max_z = pos[2] + scale[2]/2
            
            # 射线与立方体相交检测（简化版）
            # 实际应用中应使用更精确的射线-立方体相交算法
            if (min_x <= ray[0][0] <= max_x and
                min_y <= ray[0][1] <= max_y and
                min_z <= ray[0][2] <= max_z):
                # 显示货物信息
                info = f"货物ID: {data['name']}\n"
                info += f"长度: {data['length']}米\n"
                info += f"宽度: {data['width']}米\n"
                info += f"高度: {data['height']}米\n"
                info += f"重量: {data['weight']}kg"
                QMessageBox.information(self, "货物信息", info)
                return